# ~*~ coding: utf-8 ~*~
import time
import json
from datetime import timedelta

from flask import request, redirect, url_for, session, current_app
from flask_login import login_user, logout_user, login_required, current_user
from flask import views, render_template
from sqlalchemy import or_

from apps.common.response import params_error, success, auth_error
from apps.extensions import db
from apps.common.utils import get_remote_address
from ..models import User, PasswordChangeLog, UserLoginLog
from ..forms import LoginForm, PasswordForm, OtpCodeForm
from ..utils import check_otp_code

__all__ = [
    'LoginView', 'LogoutView', 'ChangePasswordView', 'MfaView'
]


class MfaView(views.MethodView):
    def get(self):
        return render_template('users/auth/mfa.html')


class LoginView(views.MethodView):

    def get(self):
        return render_template('users/auth/login.html')

    def recode(self, user, reason, status):
        ip = get_remote_address()
        city = '-'
        log = {
            'user': "{} ({})".format(user.name, user.username) if user else '-',
            'ip': ip,
            'city': city,
            'mfa': user.otp_level if user else -1,
            'reason': reason,
            'status': status
        }
        UserLoginLog.create(**log)

    def login(self, user, is_remember):
        # 记住我, 24小时没有访问session就会失效.
        # 设置到期时间后:
        #  1>在session到期时间前没有刷新浏览器，到期后会提示需要登录;
        #  2>在session到期前刷新浏览器, 会重新计算session的到期时间.
        if is_remember:
            session.permanent = True
            current_app.permanent_session_lifetime = timedelta(minutes=24*60)
            login_user(user, remember=True, duration=timedelta(minutes=24*60))

        # 默认, 30分钟没有访问session就会失效(默认数据配置在配置文件中)
        # 设置到期时间后:
        #  1>在session到期时间前没有刷新浏览器，到期后会提示需要登录;
        #  2>在session到期前刷新浏览器, 会重新计算session的到期时间.
        else:
            session.permanent = True
            login_user(user)

        user.last_login = time.time()
        user.last_ip = get_remote_address()
        db.session.commit()

    def first(self):
        form = LoginForm(request.form)
        if form.validate_on_submit():
            user = db.session.query(User).\
                filter(or_(User.username == form.username.data.strip(), User.phone == form.username.data.strip())). \
                first()
            if not user:
                self.recode(None, UserLoginLog.REASON_NOT_EXIST, UserLoginLog.LOGIN_FAILED)
                return params_error(data={
                    'message': '该不户不存在'
                })
            if not user.is_dimission:
                self.recode(user, UserLoginLog.REASON_PASSWORD_EXPIRED, UserLoginLog.LOGIN_FAILED)
                return params_error(data={
                    'message': '您已经离职，无法登录系统！'
                })
            if not user.check_password(form.password.data.strip()):
                self.recode(user, UserLoginLog.REASON_PASSWORD, UserLoginLog.LOGIN_FAILED)
                return params_error(data={
                    'message': '用户名或密码错误'
                })

            user_info = {
                'username': form.username.data.strip(),
                'remember': form.remember.data.strip()
            }
            session['user_info'] = json.dumps(user_info)

            if user.otp_turned_on:
                return success(data={'otp_auth': user.otp_turned_on})

            is_remember = True if int(form.remember.data.strip()) else False
            self.login(user, is_remember)
            self.recode(user, UserLoginLog.REASON_NOTHING, UserLoginLog.LOGIN_SUCCESS)
            return success()
        else:
            return params_error(data=form.get_error())

    def last(self):
        form = OtpCodeForm(request.form)
        if form.validate_on_submit():
            otp_code = form.otp_code.data.strip()
            try:
                user_info = json.loads(session.get('user_info'))
            # session中没有user_info信息会触发TypeError, Session过期了或者是用户没有登录.
            except TypeError:
                self.recode(None, UserLoginLog.REASON_MFA, UserLoginLog.LOGIN_FAILED)
                return params_error(data={
                    'message': '请先登录在进行MFA验证'
                })

            user = db.session.query(User). \
                filter(or_(User.username == user_info.get('username'), User.phone == user_info.get('username'))).\
                first()

            otp_secret_key = user.otp_secret_key

            if check_otp_code(otp_secret_key, otp_code):
                is_remember = True if int(user_info.get('remember')) else False
                self.login(user, is_remember)
                self.recode(user, UserLoginLog.REASON_NOTHING, UserLoginLog.LOGIN_SUCCESS)
                return success()
            else:
                self.recode(user, UserLoginLog.REASON_MFA, UserLoginLog.LOGIN_FAILED)
                return auth_error(data={
                    'message': 'MFA验证码不正确，或者服务器端时间不对'
                })
        else:
            return params_error(data=form.get_error())

    def post(self):
        step = request.args.get('step')
        meth = getattr(self, step.lower(), None)
        return meth()


class LogoutView(views.MethodView):

    decorators = [login_required, ]

    def get(self):
        logout_user()
        if 'user_info' in session:
            session.pop('user_info')
        return redirect(url_for('users.login'))


class ChangePasswordView(views.MethodView):

    decorators = [login_required, ]

    def get(self):
        return render_template('users/auth/password.html')

    def post(self):
        form = PasswordForm(request.form)
        if form.validate_on_submit():
            # change password
            user = db.session.query(User).filter(User.id == current_user.id).first()
            user.set_password(form.password.data.strip())
            user.update(commit=False)
            # log recode
            log = {
                "user": "{} ({})".format(user.name, user.username),
                'ip': get_remote_address(),
                'change_by': "{} ({})".format(current_user.name, current_user.username),
            }
            PasswordChangeLog.create(commit=False, **log)
            # commit
            db.session.commit()

            logout_user()
            return success()
        else:
            return params_error(data=form.get_error())
